/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.security.wycheproof;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest;
import com.google.security.wycheproof.WycheproofRunner.ProviderType;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Testing ECIES.
 *
 * <p>The tests in this class mainly cover the ECIES implementation of BouncyCastle. The ECIES
 * interface has changed a few times. The implementation here should work with BouncyCastle v 1.71.
 * It appears that the ECIES implementation in BouncyCastle was originally based on IEEE P1363.
 * Recent implementations appear to use ISO 18033-2, but the actual implementation is not yet
 * clearly defined. Hence the tests here focus on functionality.
 *
 * <p>Some algorithm parameters can be chosen. By default BouncyCastle uses KDF2 with SHA1 for the
 * key derivation AES-CBC with PKCS #5 padding and HMAC-SHA1 with a 20 byte digest. The AES and the
 * HMAC key are both 128 bits.
 *
 * <p>Some of the tests check if previous bugs have been fixed:
 *
 * <ul>
 *   <li>Previous versions of BouncyCastle implemented the variants "ECIESwithAES" and
 *       "ECIESwithDESede", which used ECB for the symmetric encryption. These variants have been
 *       removed from BouncyCastle.
 *   <li>Previous versions of BouncyCastle decrypted the symmetric ciphertext even if the MAC was
 *       incorrect and threw an exception depending on the correctness of the PKCS #5 padding.
 *       This behavior allowed adaptive chosen-ciphertext attacks.
 *   <li>Some of the encryption modes are malleable if the algorithm parameters generated by
 *       BouncyCastle are used.
 *   <li>Decryption with ByteBuffers may fail if the capacity of the ByteBuffer is smaller than the
 *       maximal possible plaintext size even if it is sufficiently large for the actual plaintext
 *       size.
 * </ul>
 *
 * @author bleichen@google.com (Daniel Bleichenbacher)
 */

// <p>TODO(bleichen):
// (1) test compressed points,
// (2) maybe test with CipherInputStream, CipherOutputStream,
// (3) BouncyCastle has a KeyPairGenerator for ECIES. Is this one different from EC?
// (4) the tests below avoid using BouncyCastle specific classes. IESParameterSpec allows
//     additional arguments (byte[] derivation and byte[] encoding). The effect of these
//     arguments has not been tested.

@RunWith(JUnit4.class)
public class EciesTest {

  // Known algorithm names for ECIES variants.
  // Currently all algorithm names in the list are from BouncyCastle v.1.69.
  private static final String[] ALGORITHM_NAMES = {
    "ECIES",
    "ECIESwithAES-CBC",
    "ECIESwithDESEDE-CBC",
    // Added in BouncyCastle version 1.69
    "ECIESwithSHA1",
    "ECIESwithSHA1andAES-CBC",
    "ECIESwithSHA1andDESEDE-CBC",
    "ECIESwithSHA256",
    "ECIESwithSHA256andAES-CBC",
    "ECIESwithSHA256andDESEDE-CBC",
    "ECIESwithSHA384",
    "ECIESwithSHA384andAES-CBC",
    "ECIESwithSHA384andDESEDE-CBC",
    "ECIESwithSHA512",
    "ECIESwithSHA512andAES-CBC",
    "ECIESwithSHA512andDESEDE-CBC"
  };

  /**
   * Returns a valid instance of AlgorithmParameters for an ECIES algorithm or null if no
   * AlgorithmParameters are necessary.
   *
   * <p>For the CBC variants the nonce is set to an all zero string. This is the same choice as
   * https://www.shoup.net/iso/std4.pdf Section 6.5.2.2.
   */
  AlgorithmParameters getAlgorithmParameters(String algorithmName) throws Exception {
    String paramsHex;
    switch (algorithmName.toUpperCase(Locale.ENGLISH)) {
      case "ECIES":
      case "ECIESWITHSHA1":
      case "ECIESWITHSHA256":
      case "ECIESWITHSHA384":
      case "ECIESWITHSHA512":
        // No algorithm parameters necessary.
        return null;
      case "ECIESWITHAES-CBC":
      case "ECIESWITHSHA1ANDAES-CBC":
      case "ECIESWITHSHA256ANDAES-CBC":
      case "ECIESWITHSHA384ANDAES-CBC":
      case "ECIESWITHSHA512ANDAES-CBC":
        // 256-bit AES key, 256-bit HMAC key, all zero nonce, no compression
        paramsHex = "301f02020100301602020100041000000000000000000000000000000000010100";
        break;
      case "ECIESWITHDESEDE-CBC":
      case "ECIESWITHSHA1ANDDESEDE-CBC":
      case "ECIESWITHSHA256ANDDESEDE-CBC":
      case "ECIESWITHSHA384ANDDESEDE-CBC":
      case "ECIESWITHSHA512ANDDESEDE-CBC":
        // 24 byte 3DES key, 256-bit HMAC key, all zero nonce, no compression
        paramsHex = "301702020100300e020200c004080000000000000000010100";
        break;
      default:
        fail("Unknown algorithm:" + algorithmName);
        return null;
    }
    AlgorithmParameters params = AlgorithmParameters.getInstance("ECIES");
    params.init(TestUtil.hexToBytes(paramsHex), "ASN.1");
    return params;
  }

  /**
   * Returns the expected length of the ciphertext from a few selected algorithms.
   *
   * <p>This function will be extended as needed. Only algorithms that are used in the test below
   * are added here.
   *
   * @param algorithm the name of the ECIES algorithm
   * @param curveName the name of the curve (e.g. "secp256r1").
   * @param messageLength the size of the plaintext in bytes
   * @param compressed true if points are compressed, false otherwise
   * @return the size of the ciphertext in bytes
   */
  int expectedCiphertextLength(
      String algorithm, String curveName, int messageLength, boolean compressed) throws Exception {
    int coordinateSize;
    switch (curveName) {
      case "secp256r1":
        coordinateSize = 32;
        break;
      default:
        fail("Curve not implemented: " + curveName);
        return -1; // Compiler does not know that fail never returns.
    }
    int kemSize = compressed ? (1 + coordinateSize) : (1 + 2 * coordinateSize);
    switch (algorithm.toUpperCase(Locale.ENGLISH)) {
      case "ECIESWITHAES-CBC":
        {
          // public point || PKCS5 padded ciphertext || 20-byte HMAC-digest.
          int ciphertextSize = messageLength - messageLength % 16 + 16;
          int macSize = 20;
          return kemSize + ciphertextSize + macSize;
        }
      case "ECIESWITHDESEDE-CBC":
        {
          // public point || PKCS5 padded ciphertext || 20-byte HMAC-digest.
          int ciphertextSize = messageLength - messageLength % 8 + 8;
          int macSize = 20;
          return kemSize + ciphertextSize + macSize;
        }
      default:
        fail("Not implemented");
        return -1;
    }
  }

  /**
   * Returns a EC key pair on the given curve for testing.
   *
   * @throws AssumptionViolatedException if the key could not be constructed. This skips the test.
   */
  KeyPair getEcKeyPair(String curve) {
    try {
      ECGenParameterSpec ecSpec = new ECGenParameterSpec("curve");
      KeyPairGenerator kf = KeyPairGenerator.getInstance("EC");
      kf.initialize(ecSpec);
      return kf.generateKeyPair();
    } catch (GeneralSecurityException ex) {
      TestUtil.skipTest("Could not generate key for curve: " + curve);
      return null;
    }
  }

  /**
   * BouncyCastle has a key generation algorithm "ECIES". This algorithm generates EC keys like
   * KeyPairGenerator.getInstance("EC");
   */
  @Test
  public void testKeyGeneration() throws Exception {
    ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
    KeyPairGenerator kf;
    try {
      kf = KeyPairGenerator.getInstance("ECIES");
    } catch (NoSuchAlgorithmException ex) {
      TestUtil.skipTest("Could generate a key pair");
      return;
    }
    kf.initialize(ecSpec);
    KeyPair keyPair = kf.generateKeyPair();
    ECPrivateKey unusedPriv = (ECPrivateKey) keyPair.getPrivate();
    ECPublicKey unusedPub = (ECPublicKey) keyPair.getPublic();
  }

  /**
   * Tries to decrypt ciphertexts where the symmetric part has been corrupted. If this corruption
   * leads to distinguishable exceptions then this may indicate that the implementation is
   * vulnerable to a padding attack.
   *
   * <p>For example BouncyCastle v.1.56 was vulnerable to a padding oracle attack. (see
   * CVE-2016-1000345)
   */
  @SuppressWarnings("InsecureCryptoUsage")
  @Test
  public void testExceptions() throws Exception {
    final int kemSize = 65;
    KeyPair keyPair = getEcKeyPair("secp256r1");
    PrivateKey priv = keyPair.getPrivate();
    PublicKey pub = keyPair.getPublic();

    int testsPerformed = 0;
    for (String algorithmName : ALGORITHM_NAMES) {
      Cipher ecies;
      AlgorithmParameters params;
      try {
        ecies = Cipher.getInstance(algorithmName);
        params = getAlgorithmParameters(algorithmName);
      } catch (GeneralSecurityException ex) {
        continue;
      }
      testsPerformed++;
      byte[] message = new byte[40];
      ecies.init(Cipher.ENCRYPT_MODE, pub, params);
      byte[] ciphertext = ecies.doFinal(message);
      HashSet<String> exceptions = new HashSet<>();
      for (int byteNr = kemSize; byteNr < ciphertext.length; byteNr++) {
        for (int bit = 0; bit < 8; bit++) {
          byte[] corrupt = Arrays.copyOf(ciphertext, ciphertext.length);
          corrupt[byteNr] ^= (byte) (1 << bit);
          ecies.init(Cipher.DECRYPT_MODE, priv, params);
          try {
            ecies.doFinal(corrupt);
            fail("Decrypted modified ciphertext for " + algorithmName);
          } catch (Exception ex) {
            String exception = ex.toString();
            exceptions.add(exception);
          }
        }
      }
      assertEquals("Distinguishable exceptions for " + algorithmName, 1, exceptions.size());
    }
    if (testsPerformed == 0) {
      TestUtil.skipTest("No ECIES variants implemented");
    }
  }

  /**
   * Old versions of BouncyCastle used to implement ECIES with symmetric ciphers using ECB. Quite
   * problematic was that the algorithm names did not indicate the encryption mode and hence users
   * might have chosen these ECIES variants without suspecting any weaknesses. BouncyCastle has
   * removed these variants.
   *
   * <p>This test simply ensures that old ECIES variants using ECB no longer exist.
   */
  @Test
  public void testDeprecatedVariants() throws Exception {
    try {
      Cipher.getInstance("ECIESwithAES");
      fail("ECIESwithAES should not exist");
    } catch (NoSuchAlgorithmException ex) {
      // Expected behaviour
    }
    try {
      Cipher.getInstance("ECIESwithDESede");
      fail("ECIESwithDESede should not exist");
    } catch (NoSuchAlgorithmException ex) {
      // Expected behaviour
    }
  }

  @SuppressWarnings("InsecureCryptoUsage")
  @Test
  public void testModifyPoint() throws Exception {
    KeyPair keyPair = getEcKeyPair("secp256r1");
    PrivateKey priv = keyPair.getPrivate();
    PublicKey pub = keyPair.getPublic();
    byte[] message = "This is a long text since we need 32 bytes.".getBytes(UTF_8);
    final String algorithmName = "ECIESwithAES-CBC";
    Cipher ecies = Cipher.getInstance(algorithmName);
    AlgorithmParameters params = getAlgorithmParameters(algorithmName);
    ecies.init(Cipher.ENCRYPT_MODE, pub, params);
    byte[] ciphertext = ecies.doFinal(message);
    ciphertext[2] ^= (byte) 1;
    ecies.init(Cipher.DECRYPT_MODE, priv, params);
    try {
      ecies.doFinal(ciphertext);
      fail("This should not work");
    } catch (GeneralSecurityException ex) {
      // This is as expected
    } catch (Exception ex) {
      fail(
          "Expected subclass of java.security.GeneralSecurityException, but got: "
              + ex.getClass().getName());
    }
  }

  /**
   * This test tries to detect ECIES implementations using ECB. Using ECB leads to an ECIES
   * implementation that is not secure against chosen-ciphertext attacks. This violates the claims
   * of ECIES.
   *
   * <p>BouncyCastle used to have ECIES variants that used ECB. These variants have been deprecated.
   * This test goes through the list of known ECIES variants and tries to detect variants that use
   * ECB. The test fails if a ciphertext contains two adjacent 16 byte blocks that are equal.
   */
  @SuppressWarnings("InsecureCryptoUsage")
  @Test
  public void testNotEcb() throws Exception {
    KeyPair keyPair = getEcKeyPair("secp256r1");
    PublicKey pub = keyPair.getPublic();

    for (String algorithmName : ALGORITHM_NAMES) {
      Cipher ecies;
      try {
        ecies = Cipher.getInstance(algorithmName);
      } catch (NoSuchAlgorithmException ex) {
        continue;
      }
      byte[] message = new byte[512];
      ecies.init(Cipher.ENCRYPT_MODE, pub);
      byte[] ciphertext = ecies.doFinal(message);
      int blockSize = 16;
      for (int i = 0; i < ciphertext.length - 2 * blockSize + 1; i++) {
        byte[] block1 = Arrays.copyOfRange(ciphertext, i, i + blockSize);
        byte[] block2 = Arrays.copyOfRange(ciphertext, i + blockSize, i + 2 * blockSize);
        boolean sameBlock = Arrays.equals(block1, block2);
        assertTrue("Ciphertext repeats at position:" + i + " for " + algorithmName, !sameBlock);
      }
    }
  }

  /**
   * Some of the ECIES variants use a fixed parameter set.
   *
   * <p>This test is a simple functionality test for these variants.
   */
  @SuppressWarnings("InsecureCryptoUsage")
  public void testEncryptDecryptFixedParams(String algorithmName) throws Exception {
    KeyPair keyPair = getEcKeyPair("secp256r1");
    PublicKey pub = keyPair.getPublic();
    PrivateKey priv = keyPair.getPrivate();

    Cipher eciesA;
    Cipher eciesB;
    try {
      eciesA = Cipher.getInstance(algorithmName);
      eciesB = Cipher.getInstance(algorithmName);
    } catch (NoSuchAlgorithmException ex) {
      TestUtil.skipTest("Unsupported algorithm:" + algorithmName);
      return;
    }
    byte[] message = new byte[512];
    eciesA.init(Cipher.ENCRYPT_MODE, pub);
    byte[] ciphertext = eciesA.doFinal(message);
    eciesB.init(Cipher.DECRYPT_MODE, priv);
    byte[] decrypted = eciesB.doFinal(ciphertext);
    assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted));
  }

  @Test
  public void testEncryptDecryptEcies() throws Exception {
    testEncryptDecryptFixedParams("ECIES");
  }

  @Test
  public void testEncryptDecryptEciesWithSha1() throws Exception {
    testEncryptDecryptFixedParams("ECIESwithSHA1");
  }

  @Test
  public void testEncryptDecryptEciesWithSha256() throws Exception {
    testEncryptDecryptFixedParams("ECIESwithSHA256");
  }

  @Test
  public void testEncryptDecryptEciesWithSha384() throws Exception {
    testEncryptDecryptFixedParams("ECIESwithSHA384");
  }

  @Test
  public void testEncryptDecryptEciesWithSha512() throws Exception {
    testEncryptDecryptFixedParams("ECIESwithSHA512");
  }

  /**
   * Encrypts and decrypts a message with an ECIES variant that requires algorithm parameters.
   *
   * <p>Algorithms such as ECIESwithSHA256andAES-CBC in BouncyCastle do not include the algorithm
   * parameters in the MAC. In particular, the nonce is not included. Hence if each encryption
   * selects a random parameter set and sends the parameter togother with the ciphertext then the
   * resulting encryption mode is malleable.
   *
   * <p>On the other hand making the parameters a property of the key is not a big problem. Since
   * each encryption results uses a new key reusing a nonce is not a big problem (apart from maybe
   * multi-user attacks against setups with small key sizes).
   *
   * <p>This test is a simple functionality check for ECIES with a fixed parameter set.
   *
   * @param algorithmName the name of an ECIES algorithm.
   */
  @SuppressWarnings("InsecureCryptoUsage")
  public void testEncryptDecryptWithParams(String algorithmName) throws Exception {
    KeyPair keyPair = getEcKeyPair("secp256r1");
    PublicKey pub = keyPair.getPublic();
    PrivateKey priv = keyPair.getPrivate();

    AlgorithmParameters params;
    try {
      params = getAlgorithmParameters(algorithmName);
    } catch (GeneralSecurityException ex) {
      TestUtil.skipTest("Unknown algorithm name:" + algorithmName);
      return;
    }

    Cipher eciesA;
    Cipher eciesB;
    try {
      eciesA = Cipher.getInstance(algorithmName);
      eciesB = Cipher.getInstance(algorithmName);
    } catch (NoSuchAlgorithmException ex) {
      TestUtil.skipTest("Algorithm not supported: " + algorithmName);
      return;
    }
    byte[] message = new byte[512];
    eciesA.init(Cipher.ENCRYPT_MODE, pub, params);
    byte[] ciphertext = eciesA.doFinal(message);
    eciesB.init(Cipher.DECRYPT_MODE, priv, params);
    byte[] decrypted = eciesB.doFinal(ciphertext);
    assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted));
  }

  @Test
  public void testEncryptDecryptAesCbc() throws Exception {
    testEncryptDecryptWithParams("ECIESwithAES-CBC");
  }

  @Test
  public void testEncryptDecryptSha1AndAesCbc() throws Exception {
    testEncryptDecryptWithParams("ECIESwithSHA1andAES-CBC");
  }

  @Test
  public void testEncryptDecryptSha256andAesCbc() throws Exception {
    testEncryptDecryptWithParams("ECIESwithSHA256andAES-CBC");
  }

  @Test
  public void testEncryptDecryptSha384AesCbc() throws Exception {
    testEncryptDecryptWithParams("ECIESwithSHA384andAES-CBC");
  }

  @Test
  public void testEncryptDecryptSha512AndAesCbc() throws Exception {
    testEncryptDecryptWithParams("ECIESwithSHA512andAES-CBC");
  }

  @Test
  public void testEncryptDecryptDesedeCbc() throws Exception {
    testEncryptDecryptWithParams("ECIESwithDESEDE-CBC");
  }

  /**
   * Tests the malleability of ECIES implementations.
   *
   * <p>The test is based on a potentially deceptive interface in BouncyCastle. The issue is that
   * some ECIES variants generate randomized algorithm parameters. In particular, the CBC variants
   * generate and use a random nonce for the encryption. It is tempting to retrieve the algorithm
   * parameters used for encryption with .getParameters() and send them together with the ciphertext
   * to the receiver. The test checks if this usage pattern leads to malleable ciphertexts.
   *
   * <p>The expectation is that ECIES implementations either do not generate randomized parameters
   * or that the randomized parameters are included in the integrity check.
   */
  @NoPresubmitTest(
      providers = {ProviderType.BOUNCY_CASTLE},
      bugs = {"b/238881562"})
  @SuppressWarnings("InsecureCryptoUsage")
  @Test
  public void testMalleability() throws Exception {
    KeyPair keyPair = getEcKeyPair("secp256r1");
    PublicKey pub = keyPair.getPublic();
    PrivateKey priv = keyPair.getPrivate();

    int testsPerformed = 0;
    int failures = 0;
    for (String algorithmName : ALGORITHM_NAMES) {
      Cipher eciesA;
      Cipher eciesB;
      try {
        eciesA = Cipher.getInstance(algorithmName);
        eciesB = Cipher.getInstance(algorithmName);
      } catch (NoSuchAlgorithmException ex) {
        continue;
      }
      testsPerformed++;
      byte[] message = new byte[32];
      eciesA.init(Cipher.ENCRYPT_MODE, pub);
      byte[] ciphertext = eciesA.doFinal(message);

      // Tries to generate a different test of parameters.
      eciesB.init(Cipher.ENCRYPT_MODE, pub);
      AlgorithmParameters paramsB = eciesB.getParameters();

      // Tries to decrypt with the (possibly) modified parameters.
      byte[] decrypted;
      try {
        eciesB.init(Cipher.DECRYPT_MODE, priv, paramsB);
        decrypted = eciesB.doFinal(ciphertext);
      } catch (IllegalArgumentException | BadPaddingException ex) {
        // Correct behavior if eciesA and eciesB use distinct parameters.
        continue;
      }
      // Otherwise check if modifying the parameters made the encryption
      // mode malleable.
      if (!Arrays.equals(message, decrypted)) {
        System.out.println(algorithmName + " is malleable");
        System.out.println("message:  " + TestUtil.bytesToHex(message));
        System.out.println("decrypted:" + TestUtil.bytesToHex(decrypted));
        failures++;
      }
    }
    assertEquals("Malleable ECIES algorithms found", 0, failures);
    if (testsPerformed == 0) {
      TestUtil.skipTest("No tests performed");
    }
  }

  /**
   * Encryption with ByteBuffers. This test failed with BouncyCastle v 1.52 probably because of this
   * bug http://www.bouncycastle.org/jira/browse/BJA-577
   */
  @Test
  public void testByteBuffer() throws Exception {
    KeyPair keyPair = getEcKeyPair("secp256r1");
    PrivateKey priv = keyPair.getPrivate();
    PublicKey pub = keyPair.getPublic();
    byte[] message = "Hello".getBytes(UTF_8);
    final String algorithmName = "ECIESwithAES-CBC";
    AlgorithmParameters params = getAlgorithmParameters(algorithmName);

    // Encryption
    Cipher cipher = Cipher.getInstance(algorithmName);
    cipher.init(Cipher.ENCRYPT_MODE, pub, params);
    ByteBuffer ptBuffer = ByteBuffer.wrap(message);
    ByteBuffer ctBuffer = ByteBuffer.allocate(1024);
    cipher.doFinal(ptBuffer, ctBuffer);

    // Decryption
    ctBuffer.flip();
    cipher.init(Cipher.DECRYPT_MODE, priv, params);
    // The class java.base/share/classes/javax/crypto/CipherSpi.java
    // requires that the ByteBuffer contains at least as many bytes
    // as cipher.getOutputSize() returns. Otherwise it throws a
    // ShortBufferException. This happens even if the actually encrypted
    // message would fit in the provided ByteBuffer.
    //
    // This means that a caller has to provide a ByteBuffer with more
    // remaining bytes than actually necessary.
    int requiredBufferSize = cipher.getOutputSize(ctBuffer.remaining());
    ByteBuffer decrypted = ByteBuffer.allocate(requiredBufferSize);
    cipher.doFinal(ctBuffer, decrypted);
    decrypted.flip();
    byte[] decryptedBytes = new byte[decrypted.remaining()];
    decrypted.get(decryptedBytes);
    assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decryptedBytes));
  }

  /**
   * Cipher.doFinal(ByteBuffer, ByteBuffer) should be copy-safe according to
   * https://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html
   *
   * <p>This test tries to verify this.
   */
  public void testByteBufferAlias(String algorithm) throws Exception {
    byte[] message = "Hello".getBytes(UTF_8);
    String curveName = "secp256r1";
    KeyPair keyPair = getEcKeyPair(curveName);
    Cipher ecies = Cipher.getInstance(algorithm);
    AlgorithmParameters params = getAlgorithmParameters(algorithm);

    int ciphertextLength = expectedCiphertextLength(algorithm, curveName, message.length, false);
    byte[] backingArray = new byte[ciphertextLength];
    ByteBuffer ptBuffer = ByteBuffer.wrap(backingArray);
    ptBuffer.put(message);
    ptBuffer.flip();

    ecies.init(Cipher.ENCRYPT_MODE, keyPair.getPublic(), params);
    ByteBuffer ctBuffer = ByteBuffer.wrap(backingArray);
    ecies.doFinal(ptBuffer, ctBuffer);
    ctBuffer.flip();

    ecies.init(Cipher.DECRYPT_MODE, keyPair.getPrivate(), params);
    byte[] decrypted = ecies.doFinal(backingArray, 0, ctBuffer.remaining());
    assertEquals(TestUtil.bytesToHex(message), TestUtil.bytesToHex(decrypted));
  }

  @Test
  public void testByteBufferAliasAesCbc() throws Exception {
    testByteBufferAlias("ECIESWithAES-CBC");
  }

  @Test
  public void testByteBufferAliasDesedeCbc() throws Exception {
    testByteBufferAlias("ECIESWithDESEDE-CBC");
  }

  /**
   * Old versions of BouncyCastle accepted algorithm names with paddings and simply ignored the
   * padding in the algorithm name (e.g., always used PKCS5Padding). This was confusing. The
   * situation is better now: ECIESWithAES-CBC/None/NoPadding is still accepted as algorithm name
   * (and uses PKCS5Padding). Other than this, no irregular names are accepted.
   */
  @Test
  public void testInvalidPaddings() throws Exception {
    String[] invalidNames = {
      "ECIESWithAES/CTR/NoPadding",
      "ECIESWithAES/CBC/NoPadding",
      "ECIESWithAES-CBC/None/Pkcs1Padding",
      "ECIESWithAES-CBC/None/iso10126padding",
      "ECIESWithAES-CBC/None/iso10126dpadding",
      "ECIESWithAES-CBC/CTR/NoPadding",
      "ECIESWithAES-CBC/GCM/NoPadding",
    };

    for (String name : invalidNames) {
      try {
        Cipher.getInstance(name);
        fail("Cipher implements invalid algorithm name: " + name);
      } catch (NoSuchAlgorithmException ex) {
        // expected
      }
    }
  }
}
